/*
 *   Copyright (C) 2009 Ultratronik Entwicklungs GmbH 
 *
 *     - Author: Sören Wellhöfer <soeren.wellhoefer@gmx.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *
 * Compilation:
 *   gcc -o flashread flashread.c 
 *
 * Notes:
 *   Run as root.
 *
 * Abstract:
 *   This program continuously reads random-sized chunks of bytes from
 *   random places at randomly chosen intervals from MTD flash devices
 *   under Linux.
 *
 *   For most of the above mentioned variables there exists a maximum
 *   value for each. The calculated random values will be settled in the
 *   range from zero to the specific maximum. Max values may be supplied
 *   on the command line.
 * 
 *   The max chunk size being read defaults to the erase size of the flash
 *   as it usually represents the minimum amount of data which can be read
 *   from the flash. The default maximum read interval value is one second.
 *   
 *   The intention of this program is to create a situation of extensive
 *   flash access for benchmarking purposes.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <stdint.h>
#include <sys/stat.h>

/* Constants */
#define DEFAULT_MAXINTERVAL 1000 * 1000; /* us */
const char *usg = "%s [-maxsleep usec] [-maxchunk bytes] [-verbose] mtdN\n";
const char *prog;

/* Macros */
#define TO_KB(B) (((double)B) / 1024.0)

/* Flags */
enum {
	F_VERBOSE = (unsigned int)(1<<0)
};
unsigned int flags;

/* Definitions */
struct mtdb_info {
	char name[32];
	int size;
	int erase_size;
};

/* Globals */
char *mtd;
char dev_file[64];
size_t max_chunk;
useconds_t max_sleep;

/* Prototypes */
static void* memalloc(size_t);

/* Implementation */
static inline void usage()
{
	printf(usg, prog);
}

static void errmsg(char *frm, int with_errno, ...)
{
	static const char *err = "error";
	static const char *format = "%s: %s\n";
	size_t len;
	char *pre;
	va_list l;

	va_start(l, with_errno);

	len = strlen(err) + strlen(format) + strlen(frm);
	pre = (char*)memalloc((len + 1) * sizeof(char));
	pre[len] = '\0';
	
	sprintf(pre, format, err, frm);
	vfprintf(stderr, pre, l);

	if(with_errno)
		fprintf(stderr, "(%s)\n", strerror(errno));

	free(pre);
	va_end(l);
}

static void* memalloc(size_t s)
{
	void *mem = (void*)malloc(s);
	if(!mem) {
		fprintf(stderr, "out of memory\n");
		exit(-1);
	}
	return mem;
}

static inline void dump_mtdb_info(struct mtdb_info *inf)
{
	printf("name=\"%s\", size=%.2f kB, erase_size=%.2f kB\n\n",
	       inf->name, TO_KB(inf->size), TO_KB(inf->erase_size));
}

static void get_mtdb_info(struct mtdb_info *inf)
{
	static const char *fname = "/proc/mtd";
	char buf[128];
	FILE *proc;

	if((proc = fopen(fname, "r")) == NULL) {
		errmsg("unable to open %s", 1, fname);
		exit(-1);
	}

	while(!feof(proc)) {
		fgets(buf, sizeof buf, proc);
		
		if(ferror(proc)) { 
			errmsg("reading %s failed", 1, fname);
			exit(-1);
		}

		if(!strncmp(mtd, buf, strlen(mtd))) {
			if(sscanf(buf, "%*s %x %x %s", &inf->size, &inf->erase_size, inf->name) != 3) {
				errmsg("cannot obtain information for %s from %s", 1, mtd, fname);
				exit(-1);
			}
			return;
		}
	}

	errmsg("%s not found in %s", 0, mtd, fname);
	exit(-1);
}

static uint32_t random_long(int not_signed)
{
	static const char *random_file = "/dev/urandom";
	static int fd;
	uint32_t random;
	
	if(!fd) {
		fd = open(random_file, O_RDONLY);
		if(fd < 0) {
			errmsg("cannot open %s for reading", 1, random_file);
			exit(-1);
		}
	}

	size_t have_read = 0, to_read = sizeof(long int);

	for(; to_read > 0; to_read -= have_read) {

		have_read = read(fd, ((char*)&random) + have_read, to_read);

		if(have_read < 0) {
			errmsg("reading from %s failed", 1, random_file);
			exit(-1);
		}
	}

	return not_signed ? random & (~0L >> 1) : random;
}

static void random_chunk_read(struct mtdb_info *inf)
{
	char *buf;
	int fd;

	if((fd = open(dev_file, O_RDONLY)) < 0) {
		errmsg("cannot open %s for reading", 1, dev_file);
		exit(-1);
	}

	buf = (char*)memalloc(max_chunk);
	
	for(;;) {
		long int rnd_seek, rnd_chunk, rnd_sleep;
		
		rnd_chunk = random_long(1) % max_chunk;
		rnd_seek = random_long(1) % (inf->size - rnd_chunk);
		rnd_sleep = random_long(1) % max_sleep;

		if(flags & F_VERBOSE)
			printf("rnd_chunk=%ld B, rnd_seek=%ld B, rnd_sleep=%ld ms\n",
			       rnd_chunk, rnd_seek, rnd_sleep / 1000);
		
		if(lseek(fd, (off_t)rnd_seek, SEEK_SET) < 0) {
			errmsg("seek in %s failed: fd=%d", 1, dev_file, fd);
			exit(-1);
		}

		if(read(fd, buf, rnd_chunk) < 0) {
			errmsg("reading from %s failed: fd=%d", 1, dev_file, fd);
			exit(-1);
		}
		
		usleep(rnd_sleep);
	}

	/* Not reached */	   
	close(fd);
	free(buf);
}

int main(int argc, char **argv)
{
	prog = *argv++;
	
	if(argc < 2) {
		usage();
		return -1;
	}

	if(geteuid() != 0)
		fprintf(stderr, "warning: not running as root\n");

	max_sleep = DEFAULT_MAXINTERVAL;
	max_chunk = 0;
	mtd = NULL;
	
	for( ; --argc > 0; argv++) {

		if(!strcmp("-maxchunk", *argv)) {
			
			if((max_chunk = atoi(*++argv)) == 0) {
				errmsg("not a valid chunk size: %s", 0, *argv);
				return -1;
			}
			argc--;
			
		} else if(!strcmp("-maxsleep", *argv)) {
			
			if((max_sleep = atoi(*++argv)) == 0) {
				errmsg("not a valid sleep value: %s", 0, *argv);
				return -1;
			}
			argc--;
			
		} else if(!strcmp("-verbose", *argv)) {

			flags |= F_VERBOSE;
		} else {
			if(strlen(*argv) > 3 && !strncmp("mtd", *argv, 3)) {
				mtd = *argv;
				snprintf(dev_file, sizeof dev_file, "/dev/%s", mtd);
				
			} else {
				errmsg("device string must be of form \"mtdN\" (N >= 0)\n", 0);
			}
		}
	}

	if(!mtd) {
		errmsg("no memory technology device given", 0);
		usage();
		return -1;
	}
	
	struct mtdb_info inf;
	memset(&inf, 0, sizeof(struct mtdb_info));
	get_mtdb_info(&inf);

	if(flags & F_VERBOSE) dump_mtdb_info(&inf);

	if(!max_chunk) max_chunk = inf.erase_size;

	random_chunk_read(&inf);

	return 0;
}
